#include "Graphics/PiccoloVKDevice.h"

#include <Graphics/PiccoloVKCommandBuffer.h>

#include "Graphics/PiccoloVKGraphicsContext.h"
#include "Graphics/PiccoloVKQueue.h"

namespace Piccolo
{
    const DeviceFeature requestedExtensions[] =
    {
        {VK_KHR_SWAPCHAIN_EXTENSION_NAME, true},
#ifdef VK_USE_PLATFORM_WIN32_KHR
#elif PICCOLO_ENGINE_PLATFORM_MACOS
        {"VK_KHR_portability_subset", true}
#elif PICCOLO_ENGINE_PLATFORM_LINUX
#endif
    };

    PiccoloVKDevice::PiccoloVKDevice(PiccoloVKGraphicsContext *context, uint32_t graphicQueueCount, uint32_t presentQueueCount, const PiccoloVKSettings &settings) : mSettings(settings), mContext(context)
    {
        if (!context)
        {
            LOG_E("Must create a vulkan graphic context before create device.");
            return;
        }

        QueueFamilyInfo graphicQueueFamilyInfo = context->GetGraphicQueueFamilyInfo();
        QueueFamilyInfo presentQueueFamilyInfo = context->GetPresentQueueFamilyInfo();

        if (graphicQueueCount > graphicQueueFamilyInfo.queueCount)
        {
            LOG_E("this queue family has {0} queue, but request {1}", graphicQueueFamilyInfo.queueCount, graphicQueueCount);
            return;
        }
        if (presentQueueCount > presentQueueFamilyInfo.queueCount)
        {
            LOG_E("this queue family has {0} queue, but request {1}", presentQueueFamilyInfo.queueCount, presentQueueCount);
            return;
        }

        // 优先级，保证所有的图像处理队列优先级小于显示队列
        std::vector<float> graphicQueuePriorities(graphicQueueCount, 0.f);
        std::vector<float> presentQueuePriorities(presentQueueCount, 1.f);

        // 是否共用队列
        bool bSameQueueFamilyIndex = context->IsSameGraphicPresentQueueFamily();
        uint32_t sameQueueCount = graphicQueueCount;
        if (bSameQueueFamilyIndex)
        {
            sameQueueCount += presentQueueCount;
            if (sameQueueCount > graphicQueueFamilyInfo.queueCount)
            {
                sameQueueCount = graphicQueueFamilyInfo.queueCount;
            }
            // 追加过去
            graphicQueuePriorities.insert(graphicQueuePriorities.end(), presentQueuePriorities.begin(), presentQueuePriorities.end());
        }

        VkDeviceQueueCreateInfo queueInfo[2] =
        {
            {
                .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
                .pNext = nullptr,
                .flags = 0,
                .queueFamilyIndex = static_cast<uint32_t>(graphicQueueFamilyInfo.queueFamilyIndex),
                .queueCount = sameQueueCount,
                .pQueuePriorities = graphicQueuePriorities.data()
            }
        };
        if (!bSameQueueFamilyIndex)
        {
            queueInfo[1] =
            {
                .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
                .pNext = nullptr,
                .flags = 0,
                .queueFamilyIndex = static_cast<uint32_t>(presentQueueFamilyInfo.queueFamilyIndex),
                .queueCount = presentQueueCount,
                .pQueuePriorities = presentQueuePriorities.data()
            };
        }

        // 设备需要支持交换链
        uint32_t availableExtensionCount;
        CALL_VK(vkEnumerateDeviceExtensionProperties(context->GetPhysicalDevice(), "", &availableExtensionCount, nullptr));
        VkExtensionProperties availableExtensions[availableExtensionCount];
        CALL_VK(vkEnumerateDeviceExtensionProperties(context->GetPhysicalDevice(), "", &availableExtensionCount, availableExtensions));

        // 加载必要扩展
        uint32_t enableExtensionCount;
        const char* enableExtensions[32];
        if (!checkDeviceFeatures("Device Extension", true, availableExtensionCount, availableExtensions, ARRAY_SIZE(requestedExtensions),
            requestedExtensions, &enableExtensionCount, enableExtensions))
        {
            return;
        }


        VkDeviceCreateInfo deviceCreateInfo =
        {
            .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .queueCreateInfoCount = static_cast<uint32_t>(bSameQueueFamilyIndex ? 1 : 2),
            .pQueueCreateInfos = queueInfo,
            .enabledLayerCount = 0,
            .ppEnabledLayerNames = nullptr,
            .enabledExtensionCount = enableExtensionCount,
            .ppEnabledExtensionNames = enableExtensionCount > 0 ? enableExtensions : nullptr,
            .pEnabledFeatures = nullptr
        };
        // 创建逻辑设备
        CALL_VK(vkCreateDevice(context->GetPhysicalDevice(), &deviceCreateInfo, nullptr, &mDevice));
        LOG_T("VkDevice: {0}", (void*)mDevice);

        // 获取队列
        for (int i = 0; i < graphicQueueCount; ++i)
        {
            VkQueue queue;
            vkGetDeviceQueue(mDevice, graphicQueueFamilyInfo.queueFamilyIndex, i, &queue);
            mGraphicQueues.push_back(std::make_shared<PiccoloVKQueue>(graphicQueueFamilyInfo.queueFamilyIndex, i, queue, false));
        }
        for (int i = 0; i < presentQueueCount; ++i)
        {
            VkQueue queue;
            vkGetDeviceQueue(mDevice, presentQueueFamilyInfo.queueFamilyIndex, i, &queue);
            mPresentQueues.push_back(std::make_shared<PiccoloVKQueue>(presentQueueFamilyInfo.queueFamilyIndex, i, queue, true));
        }

        CreatePipelineCache();
        CreateDefaultCommandPool();
    }

    PiccoloVKDevice::~PiccoloVKDevice()
    {
        vkDeviceWaitIdle(mDevice);
        mDefaultCommandPool = nullptr;
        VK_D(PipelineCache, mDevice, mPipelineCache);
        vkDestroyDevice(mDevice, nullptr);
    }

    uint32_t PiccoloVKDevice::GetMemoryIndex(VkMemoryPropertyFlags memProps, uint32_t memTypeBits) const
    {
        VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProps = mContext->GetPhysicalDeviceMemoryProperties();
        if (physicalDeviceMemoryProps.memoryTypeCount == 0)
        {
            LOG_E("Physical device memory type count is 0");
            return -1;
        }
        for (int i = 0; i < physicalDeviceMemoryProps.memoryTypeCount; ++i)
        {
            if (memTypeBits & (1 << i) && (physicalDeviceMemoryProps.memoryTypes[i].propertyFlags & memProps) == memProps)
            {
                return i;
            }
        }
        LOG_E("Can not find memory type index: type bit: {0}", memTypeBits);
        return 0;
    }

    VkCommandBuffer PiccoloVKDevice::CreateAndBeginSingleCommandBuffer()
    {
        VkCommandBuffer commandBuffer = mDefaultCommandPool->AllocateSingleCommandBuffer();
        mDefaultCommandPool->BeginCommandBuffer(commandBuffer);
        return commandBuffer;
    }

    void PiccoloVKDevice::EndSingleCommandBuffer(VkCommandBuffer commandBuffer)
    {
        mDefaultCommandPool->EndCommandBuffer(commandBuffer);
        PiccoloVKQueue* queue = GetFirstGraphicQueue();
        queue->Submit({ commandBuffer });
        queue->WaitIdle();
    }

    void PiccoloVKDevice::CreatePipelineCache()
    {
        VkPipelineCacheCreateInfo pipelineCacheCreateInfo =
        {
            .sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0
        };
        CALL_VK(vkCreatePipelineCache(mDevice, &pipelineCacheCreateInfo, nullptr, &mPipelineCache));
    }

    void PiccoloVKDevice::CreateDefaultCommandPool()
    {
        mDefaultCommandPool = std::make_shared<PiccoloVKCommandPool>(this, mContext->GetGraphicQueueFamilyInfo().queueFamilyIndex);
    }

    VkResult PiccoloVKDevice::CreateSimpleSampler(VkFilter filter, VkSamplerAddressMode addressMode, VkSampler* outSampler)
    {
        VkSamplerCreateInfo samplerInfo =
        {
            .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0,
            .magFilter = filter,
            .minFilter = filter,
            .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR,
            .addressModeU = addressMode,
            .addressModeV = addressMode,
            .addressModeW = addressMode,
            .mipLodBias = 0,
            .compareEnable = VK_FALSE,
            .compareOp = VK_COMPARE_OP_NEVER,
            .minLod = 0,
            .maxLod = 1,
            .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK,
            .unnormalizedCoordinates = VK_FALSE
        };
        return vkCreateSampler(mDevice, &samplerInfo, nullptr, outSampler);
    }
}
